package com.xieh.springbootminio.controller;

import cn.hutool.core.codec.Base64Encoder;
import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xieh.springbootminio.prop.MinioProperties;
import com.xieh.springbootminio.vo.FileVo;
import com.xieh.springbootminio.vo.PageUtil;
import com.xieh.springbootminio.vo.Res;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@RestController
@RequestMapping("/minio")
public class MinioController {
    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioProperties minioProperties;

    private List<String> docList = new ArrayList<>();

    /**
     * 获取文件列表
     * @param map
     * @return
     * @throws Exception
     */
    @GetMapping("/list")
    public List<Object> list(ModelMap map) throws Exception {
        ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder().bucket(minioProperties.getBucketName()).build();
        Iterable<Result<Item>> myObjects = minioClient.listObjects(listObjectsArgs);
//        Iterable<Result<Item>> myObjects = minioClient.listObjects(minioProperties.getBucketName());
        Iterator<Result<Item>> iterator = myObjects.iterator();
        List<Object> items = new ArrayList<>();
        String format = "{'fileName':'%s','fileSize':'%s'}";
        while (iterator.hasNext()) {
            Item item = iterator.next().get();
            items.add(JSON.parse(String.format(format, item.objectName(), formatFileSize(item.size()))));
        }
        return items;
    }

    /**
     * 查看所有的上传文件列表
     * @param pageNum
     * @param pageSize
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping(value = "/fileList", method = RequestMethod.GET, produces = {"application/json;charset=utf-8"})
    public String getFileList(Integer pageNum, Integer pageSize) throws Exception {
        System.out.println("----------------------------------------------------------------");
        List<Bucket> buckets = minioClient.listBuckets();
        List<FileVo> list = new ArrayList<>();
        if (!buckets.isEmpty()) {
            for (int i = 0; i < buckets.size(); i++) {
                Bucket s = buckets.get(i);
                listDocs(s.name(),"", list);
            }
        }
        JSONObject res = new JSONObject();
        res.put("code", 200);
        res.put("message", "获取文件列表成功");
        // 按最后上传时间排序
        list.sort(new Comparator<FileVo>() {
            @Override
            public int compare(FileVo o1, FileVo o2) {
                return o2.getUpdateTime().compareTo(o1.getUpdateTime());
            }
        });
        // 分页
        List returnList = PageUtil.startPage(list, pageNum, pageSize);
        res.put("list", returnList);
        ObjectMapper mapper = new ObjectMapper();
        String s = mapper.writeValueAsString(res);
        return s;
    }

    public void listDocs(String bucketName,String prefix, List<FileVo> list){
        DecimalFormat df = new DecimalFormat("0.00");
        Iterable<Result<Item>> listObjects=new ArrayList<>();
        if(StringUtils.hasLength(prefix)){
            /*listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(prefix)
                    .build());*/
            listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(prefix)
                    .recursive(true)//是否递归
                    .build());
        }else {
            /*listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .build());*/
            listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .recursive(true)//是否递归
                    .build());
        }
        try{
            for (Result<Item> result : listObjects) {
                Item item = result.get();
                if(item.isDir()){
//                    docList.add(item.objectName());
//                    listDocs(bucketName,item.objectName(),list);
                }else{
                    FileVo fileVo = new FileVo();
                    fileVo.setBucketName(bucketName);  // 文件夹名称
                    String name = URLDecoder.decode(item.objectName(),"utf-8");
                    fileVo.setFileName(name);  // 文件名称
                    fileVo.setUpdateTime(localDateTime2Date(item.lastModified().toLocalDateTime()));  // 文件上传时间
                    Long size = item.size();
                    if (size > (1024 * 1024)) {
                        fileVo.setFileSize(df.format(((double) size / 1024 / 1024)) + "MB");  // 文件大小，如果超过1M，则把单位换成MB
                    } else if (size > 1024) {
                        fileVo.setFileSize(df.format(((double) size / 1024)) + "KB"); // 文件大小，如果没超过1M但是超过1000字节，则把单位换成KB
                    } else {
                        fileVo.setFileSize(size + "bytes");  // // 文件大小，如果没超过1000字节，则把单位换成bytes
                    }
                    list.add(fileVo);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public Iterable<Result<Item>> listObjects(String bucketName,String prefix,String title,boolean recursive){
        try {
            Iterable<Result<Item>> listObjects=new ArrayList<>();
            if(StringUtils.hasLength(title)){
                listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .startAfter(title)
                        .build());
            }
            else if(StringUtils.hasLength(prefix)){
                listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .recursive(recursive) // 递归
                        .build());
            }else {
                listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .recursive(recursive) // 递归
                        .build());
            }
            return listObjects;
        } catch (Exception e) {
            System.out.println("Error occurred: " + e);
            e.printStackTrace();
            return null;
        }
    }

    public Date localDateTime2Date( LocalDateTime localDateTime){
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = localDateTime.atZone(zoneId);
        Date date = Date.from(zdt.toInstant());
        Calendar cal=Calendar.getInstance();
        cal.setTime(date);
        // 由于获取的时间存在时间差，我这里手动加上16小时
        cal.add(Calendar.HOUR_OF_DAY, 16);
        date = cal.getTime();
        return date;
    }

    /**
     * 上传文件
     * @param file 要上传的文件
     * @return
     */
    @PostMapping("/upload")
    public Res upload(@RequestParam(name = "file", required = false) MultipartFile[] file) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, ErrorResponseException, ServerException {
        Res res = new Res();
        res.setCode(500);

        if (file == null || file.length == 0) {
            res.setMessage("上传文件不能为空");
            return res;
        }

        //存入bucket不存在则创建，并设置为只读
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build())){
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());
//        if (!minioClient.bucketExists(minioProperties.getBucketName())) {
//            minioClient.makeBucket(minioProperties.getBucketName());
            //minioClient.setBucketPolicy(minioProperties.getBucketName(), "*.*", PolicyType.READ_ONLY);
        }

        List<String> orgfileNameList = new ArrayList<>(file.length);

        for (MultipartFile multipartFile : file) {
            // 获取真实文件名
            String orgfileName = multipartFile.getOriginalFilename();
            // 父目录名
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 文件存储的目录结构
            String objectName = sdf.format(new Date()) + "/" + orgfileName;

            try {
                InputStream in = multipartFile.getInputStream();
                // 开始上传
                PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName).bucket(minioProperties.getBucketName()).contentType("application/octet-stream").stream(in,in.available(),-1).build();
                minioClient.putObject(objectArgs);
//                minioClient.putObject(minioProperties.getBucketName(), objectName, in, new PutObjectOptions(in.available(), -1));
                in.close();
                // 完成上传以后再保存文件完整路径
                String fullFilePath = minioProperties.getEndpoint() + "/" + minioProperties.getBucketName() + "/" + objectName;
                orgfileNameList.add(fullFilePath);
            } catch (Exception e) {
                log.error(e.getMessage());
                res.setMessage("上传失败");
                return res;
            }
        }

        Map<String, Object> data = new HashMap<String, Object>();
        data.put("bucketName", minioProperties.getBucketName());
        data.put("fileName", orgfileNameList);
        res.setCode(200);
        res.setMessage("上传成功");
        res.setData(data);
        return res;
    }

    /**
     * 将指定文件以压缩包下载
     * @param response
     * @param fileName 要下载的文件名
     */
    @RequestMapping(value = "/downLoadZip", method = RequestMethod.GET)
    public void downLoadZip(HttpServletResponse response, @RequestParam("fileName") String fileName){
        System.out.println("=============================="+fileName);
        ZipOutputStream zipos = null;
        DataOutputStream os = null;
        InputStream is = null;
        try {
            response.reset();
            String zipName = new String(URLEncoder.encode(fileName, "UTF-8").getBytes(), StandardCharsets.ISO_8859_1);
            response.setHeader("Content-Disposition", "attachment;fileName=\"" + zipName + ".zip\"");
            response.setContentType("application/octet-stream; charset=UTF-8");
            zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
            zipos.setMethod(ZipOutputStream.DEFLATED); //设置压缩方法
            try {
                zipos.putNextEntry(new ZipEntry(fileName));
                os = new DataOutputStream(zipos);
                GetObjectArgs getObjectArgs = GetObjectArgs.builder().object(fileName).bucket(minioProperties.getBucketName()).build();
                is = minioClient.getObject(getObjectArgs);
//                is = minioClient.getObject(minioProperties.getBucketName(), fileName);
                byte[] b = new byte[1024];
                int length = 0;
                while((length = is.read(b))!= -1){
                    os.write(b, 0, length);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            is.close();
            os.flush();
            os.close();
            zipos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (zipos != null) {
                try {
                    zipos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 将bucket下所有文件以压缩包下载
     * @param response
     * @param bucket 要下载的bucket
     */
    @RequestMapping(value = "/batchDownLoadZip", method = RequestMethod.GET)
    public void batchDownLoadZip(HttpServletResponse response, @RequestParam("bucket") String bucket) {
        List<String> filePaths = new ArrayList<>();
        listZips(bucket,"",filePaths);
        ZipOutputStream zipos = null;
        DataOutputStream os = null;
        InputStream is = null;
        try {
            response.reset();
            String zipName = new String(URLEncoder.encode(bucket, "UTF-8").getBytes(), StandardCharsets.ISO_8859_1);
            response.setHeader("Content-Disposition", "attachment;fileName=\"" + zipName + ".zip\"");
            response.setContentType("application/octet-stream; charset=UTF-8");
            zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
            zipos.setMethod(ZipOutputStream.DEFLATED); //设置压缩方法
            for(int i = 0;i < filePaths.size();i++) {
                String file = filePaths.get(i);
                String packageName = filePaths.get(i).replace(bucket+"/", "");
                try {
                    zipos.putNextEntry(new ZipEntry(packageName));
                    os = new DataOutputStream(zipos);
                    GetObjectArgs getObjectArgs = GetObjectArgs.builder().object(file).bucket(minioProperties.getBucketName()).build();
                    is = minioClient.getObject(getObjectArgs);
                    byte[] b = new byte[1024];
                    int length = 0;
                    while((length = is.read(b))!= -1){
                        os.write(b, 0, length);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            is.close();
            os.flush();
            os.close();
            zipos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (zipos != null) {
                try {
                    zipos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @RequestMapping(value = "/batchDownload", method = RequestMethod.GET)
    public void batchDownload(HttpServletResponse res, HttpServletRequest req, @RequestParam("bucket") String bucket){
        try{
            List<String> filePaths = new ArrayList<>();
            listZips(bucket,"",filePaths);
            BufferedOutputStream os = null;
            res.reset();
            os = new BufferedOutputStream(res.getOutputStream());
            ZipOutputStream out = new ZipOutputStream(os);
            res.setHeader("Access-Control-Allow-Origin", "*");
            for (int i=0;i<filePaths.size();i++) {
                GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucket)
                        .object(filePaths.get(i)).build();
                InputStream object = minioClient.getObject(objectArgs);
                byte buf[] = new byte[1024];
                int length = 0;
                res.setCharacterEncoding("utf-8");
                res.setContentType("application/force-download");// 设置强制下载不打开
                res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
                res.setHeader("Content-Disposition", "attachment;filename=" + filenameEncoding(bucket, req) + ".zip");
                out.putNextEntry(new ZipEntry(filePaths.get(i)));
                while ((length = object.read(buf)) > 0) {
                    out.write(buf, 0, length);
                }
            }
            out.close();
            os.close();
        }catch (Exception e){

        }
    }
    /**
     *  设置不同浏览器编码
     * @param filename 文件名称
     * @param request 请求对象
     */
    public static String filenameEncoding(String filename, HttpServletRequest request) throws UnsupportedEncodingException {
        // 获得请求头中的User-Agent
        String agent = request.getHeader("User-Agent");
        // 根据不同的客户端进行不同的编码
        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            Base64Encoder base64Encoder = new Base64Encoder();
            filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }

    @RequestMapping(value = "/apiDownloadZip", method = RequestMethod.GET)
    public void apiDownloadZip(HttpServletResponse response, @RequestParam("ids") String ids) {
        List<String> filePaths = new ArrayList<>();

        ZipOutputStream zipos = null;
        DataOutputStream os = null;
        InputStream is = null;
//        HttpRequest.post(DataCenterUtil.DATA_CENTER_TOKEN_URL + url).header("Authorization", "Bearer " + token).body(JSONUtil.toJsonStr(map)).execute().body();
    }

    /**
     * 遍历bucket下所有文件名
     * @param bucketName 要下载的bucket
     * @param prefix 前缀
     * @param filePaths 文件名列表
     */
    public void listZips(String bucketName,String prefix, List<String> filePaths){
        Iterable<Result<Item>> listObjects=new ArrayList<>();
        if(StringUtils.hasLength(prefix)){
            listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(prefix)
                    .build());
        }else {
            listObjects = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .build());
        }
        try{
            for (Result<Item> result : listObjects) {
                Item item = result.get();
                if(item.isDir()){
                    listZips(bucketName,item.objectName(),filePaths);
                }else{
                    String name = URLDecoder.decode(item.objectName(),"utf-8");
                    filePaths.add(name);  // 文件名称
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 下载指定文件
     * @param response
     * @param fileName 要下载的文件名
     */
    @RequestMapping("/download")
    public void download(HttpServletResponse res, @RequestParam("fileName") String fileName) throws IOException {
        System.out.println("=============================="+fileName);
        /*try {//文件下载到应用服务器
            minioClient.downloadObject(DownloadObjectArgs.builder().bucket(minioProperties.getBucketName()).object(fileName).filename(fileName).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }*/
        try{
            GetObjectArgs args = GetObjectArgs.builder()
                    .bucket(minioProperties.getBucketName())
                    .object(fileName)
                    .build();
            InputStream is = minioClient.getObject(args);
            res.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            StringBuilder contentDispositionValue = new StringBuilder();
            String name = "";
            //文件名截取
            if(fileName.lastIndexOf("/") != -1 ){
                name = fileName.substring(fileName.lastIndexOf("/") + 1);
            }else{
                name = fileName;
            }
            contentDispositionValue.append("attachment; filename=")
                    .append(URLEncoder.encode(name, "UTF-8"))
                    .append(";")
                    .append("filename*=")
                    .append("utf-8''")
                    .append(URLEncoder.encode(name, "UTF-8"));
            res.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
            res.setHeader("Content-disposition", contentDispositionValue.toString());
            res.setHeader("download-filename", URLEncoder.encode(name, "UTF-8"));
            byte[] b = new byte[1024];
            int length = 0;
            while((length = is.read(b))!= -1){
                res.getOutputStream().write(b, 0, length);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        /*InputStream in = null;
        try {
            String name = "";
            //文件名截取
            if(fileName.lastIndexOf("/") != -1 ){
                name = fileName.substring(fileName.lastIndexOf("/") + 1);
            }else{
                name = fileName;
            }
            System.out.println(name+"-----------------------------------------------------------");
            StatObjectArgs statObjectArgs = StatObjectArgs.builder().object(fileName).bucket(minioProperties.getBucketName()).build();
//            ObjectStat stat = minioClient.statObject(minioProperties.getBucketName(), fileName);
            StatObjectResponse stat = minioClient.statObject(statObjectArgs);

            res.setContentType(ContentType.OCTET_STREAM.toString());
            res.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(name, "UTF-8"));
            System.out.println("===================================================================");
            GetObjectArgs getObjectArgs = GetObjectArgs.builder().object(fileName).bucket(minioProperties.getBucketName()).build();
//            in = minioClient.getObject(minioProperties.getBucketName(), fileName);
            in = minioClient.getObject(getObjectArgs);
            IOUtils.copy(in, res.getOutputStream());

        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }*/
    }

    /**
     * 删除文件
     * @param fileName 要删除的文件名
     * @return
     */
    @DeleteMapping("/delete")
    public Res delete(@RequestParam("fileName") String fileName) {
        Res res = new Res();
        res.setCode(200);
        try {
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().object(fileName).bucket(minioProperties.getBucketName()).build();
            minioClient.removeObject(removeObjectArgs);
//            minioClient.removeObject(minioProperties.getBucketName(), fileName);
        } catch (Exception e) {
            res.setCode(500);
            log.error(e.getMessage());
        }
        return res;
    }


    /**
     * 生成可以预览的文件链接
     * @return
     * @throws XmlParserException
     * @throws NoSuchAlgorithmException
     * @throws InsufficientDataException
     * @throws InternalException
     * @throws InvalidResponseException
     * @throws InvalidKeyException
     * @throws ErrorResponseException
     * @throws IOException
     */
    @GetMapping("/previewList")
    public List<Object> getPreviewList() throws XmlParserException, NoSuchAlgorithmException, InsufficientDataException, InternalException, InvalidResponseException, InvalidKeyException, ErrorResponseException, IOException, ServerException {
        ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder().bucket(minioProperties.getBucketName()).build();
        Iterable<Result<Item>> myObjects = minioClient.listObjects(listObjectsArgs);
//        Iterable<Result<Item>> myObjects = minioClient.listObjects(minioProperties.getBucketName());
        Iterator<Result<Item>> iterator = myObjects.iterator();
        List<Object> items = new ArrayList<>();
        String format = "{'fileName':'%s'}";
        while (iterator.hasNext()) {
            Item item = iterator.next().get();
            // TODO 根据文件后缀名，过滤哪些是可以预览的文件
            //String bucketName, 桶名称
            // String objectName, 文件路径
            // Integer expires, 链接过期时间
            // Map<String, String> reqParams 请求参数
            // 开始生成
//            String filePath = minioClient.presignedGetObject(minioProperties.getBucketName(), item.objectName());
//            items.add(JSON.parse(String.format(format, filePath)));
        }
        return  items;
    }



    /**
     * 显示文件大小信息单位
     * @param fileS
     * @return
     */
    private static String formatFileSize(long fileS) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString = "";
        String wrongSize = "0B";
        if (fileS == 0) {
            return wrongSize;
        }
        if (fileS < 1024) {
            fileSizeString = df.format((double) fileS) + " B";
        } else if (fileS < 1048576) {
            fileSizeString = df.format((double) fileS / 1024) + " KB";
        } else if (fileS < 1073741824) {
            fileSizeString = df.format((double) fileS / 1048576) + " MB";
        } else {
            fileSizeString = df.format((double) fileS / 1073741824) + " GB";
        }
        return fileSizeString;
    }
}